package com.societyGames.flashForms
{
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;

	public class Window extends SingleChildContainer
	{
		private var _desiredWidth:Number;
		private var _desiredHeight:Number;
		private var _body:Sprite;
		private var _borderTop:Number;
		private var _borderLeft:Number;
		private var _borderBottom:Number;
		private var _borderRight:Number;
		private var _borderMoveTop:Number;
		private var _dragBounds:Rectangle;
		private var _dragType:DragType;
		private var _isLocked:Boolean; //Tracks whether mouse cursor is locked.
		private var _lastMouseX:Number;
		private var _lastMouseY:Number;
		private var _allowResize:Boolean = true;
		
		private var _header:DisplayObject;
		
		public function Window(body:Sprite, borderLeft:Number, borderRight:Number, borderTop:Number, borderBottom:Number, borderMoveTop:Number)
		{	
			this._body = body;
			this._borderLeft = borderLeft;
			this._borderRight = borderRight;
			this._borderTop = borderTop;
			this._borderBottom = borderBottom;
			this._borderMoveTop = borderMoveTop;
			
			this._body.y = 0;
			this.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
			this.addEventListener(MouseEvent.ROLL_OUT, rollOutHandler);
			this.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
			this.addChild(this._body);
			
			this._desiredWidth = width;
			this._desiredHeight = height;
		}
		
		public override function set width(value:Number):void
		{	
			if (value != this._desiredWidth)
			{
				this._desiredWidth = value;
				this._body.width = this._desiredWidth;
				recalculateChildWidth();
				recalculateHeaderWidth();
			}
		}
		
		public override function set height(value:Number):void
		{
			if (value != this._desiredHeight)
			{	
				this._desiredHeight = value;
				this._body.height = this._desiredHeight;
				recalculateChildHeight();
				recalculateHeaderHeight();
			}
		}
		
		public override function set child(value:DisplayObject):void
		{	
			super.child = value;
			this._child.x = this._borderLeft;
			this._child.y = this._borderTop + this._borderMoveTop;
			recalculateChildWidth();
			recalculateChildHeight();
		}

		//Whether you can drag the window larger or smaller.
		public function get allowResize():Boolean
		{
			return this._allowResize;
		}
		
		//Whether you can drag the window larger or smaller.
		public function set allowResize(value:Boolean):void
		{
			this._allowResize = value;
		}
		
		//Set space for the child.
		public function set innerWidth(value:Number):void
		{
			width = value + _borderLeft + _borderRight;
		}
		
		//Get space for the child.
		public function get innerWidth():Number
		{
			return _desiredWidth - (_borderLeft + _borderRight);
		}
		
		//Set space for the child.
		public function set innerHeight(value:Number):void
		{
			height = value + _borderTop + _borderMoveTop + _borderBottom;
		}
		
		//Get space for the child.
		public function get innerHeight():Number
		{
			return _desiredHeight - (_borderTop + _borderMoveTop + _borderBottom);
		}
		
		public function set header(value:DisplayObject):void
		{
			this._header = value;
			this._header.x = this._borderLeft;
			this._header.y = this._borderTop;
			this.addChild(this._header);
			recalculateHeaderWidth();
			recalculateHeaderHeight();
		}
		
		public function set dragBounds(value:Rectangle):void
		{	
			this._dragBounds = value;
		}
			
		private function recalculateChildWidth():void
		{
			if (this._child != null)
			{
				this._child.width = _borderLeft + _borderRight > this._desiredWidth ? 0 : this._desiredWidth - (_borderLeft + _borderRight);	
			}
		}
		
		private function recalculateChildHeight():void
		{
			if (this._child != null)
			{
				this._child.height = _borderTop + _borderMoveTop + _borderBottom > this._desiredHeight ? 0 : this._desiredHeight - (_borderTop + _borderMoveTop + _borderBottom);
			}
		}
		
		private function recalculateHeaderWidth():void
		{
			if (this._header != null)
			{
				this._header.width = _borderLeft + _borderRight > this._desiredWidth ? Math.max(0, this._desiredWidth - _borderLeft) : this._desiredWidth - (_borderLeft + _borderRight);
			}
		}
		
		private function recalculateHeaderHeight():void
		{
			if (this._header != null)
			{
				this._header.height = _borderTop + _borderMoveTop > this._desiredHeight ? Math.max(0, this._desiredHeight - _borderTop) : _borderMoveTop;
			}
		}
		
		private function mouseMoveHandler(event:MouseEvent = null):void
		{	
			var newDragType:DragType = null;

			if (this._allowResize)
			{
				//In the left border
				if (this.mouseX < this._borderLeft && this.mouseX > 0)
				{
					if (this.mouseY < this._borderTop)
					{
						newDragType = DragType.LeftTop;
					}
					else if (this.mouseY > this._desiredHeight - this._borderBottom)
					{
						newDragType = DragType.LeftBottom;
					}
					else
					{
						newDragType = DragType.Left;
					}
				}
				//In the right border
				else if (this.mouseX > this._desiredWidth - this._borderRight && this.mouseX < this._desiredWidth)
				{
					if (this.mouseY < this._borderTop)
					{
						newDragType = DragType.RightTop;
					}
					else if (this.mouseY > this._desiredHeight - this._borderBottom)
					{
						newDragType = DragType.RightBottom;
					}
					else
					{
						newDragType = DragType.Right;
					}
				}
				//In the top
				else if (this.mouseY < this._borderTop)
				{
					newDragType = DragType.Top;
				}
				//In the bottom
				else if (this.mouseY > this._desiredHeight - this._borderBottom && this.mouseY < this._desiredHeight)
				{
					newDragType = DragType.Bottom;
				}
			} //End if allow resize.
			//In the move area
			if (this.mouseY < this._borderTop + this._borderMoveTop && this.mouseY > this._borderTop)
			{
				newDragType = DragType.Move;
			}
			showDragType(newDragType, false);
		}
		
		private function rollOutHandler(event:MouseEvent):void
		{
			showDragType(null, false);
		}
		
		private function showDragType(dragType:DragType, shouldLock:Boolean):void
		{			
			if (this._dragType != dragType || this._isLocked != shouldLock)
			{
				this._dragType = dragType;
				this._isLocked = shouldLock;
				if (this._isLocked)
				{
					switch (this._dragType)
					{
						case null:
						case DragType.Move:
							MouseCursor.instance.unlockCursor(this);							
							break;
						case DragType.Left:
						case DragType.Right:
							MouseCursor.instance.lockCursor(Cursors.leftRightCursor, this, -Cursors.leftRightCursor.width / 2, -Cursors.leftRightCursor.height / 2);
							break;
						case DragType.Top:
						case DragType.Bottom:
							MouseCursor.instance.lockCursor(Cursors.upDownCursor, this, -Cursors.upDownCursor.width / 2, -Cursors.upDownCursor.height / 2);
							break;
						case DragType.RightTop:
						case DragType.LeftBottom:
							MouseCursor.instance.lockCursor(Cursors.leftDownCursor, this, -Cursors.upDownCursor.width / 2, -Cursors.upDownCursor.height / 2);							
							break;
						case DragType.LeftTop:
						case DragType.RightBottom:
							MouseCursor.instance.lockCursor(Cursors.rightDownCursor, this, -Cursors.upDownCursor.width / 2, -Cursors.upDownCursor.height / 2);
							break;
					}
				}
				else
				{
					switch (this._dragType)
					{
						case null:
						case DragType.Move:
							MouseCursor.instance.unsetCursor(this);
							break;
						case DragType.Left:
						case DragType.Right:
							MouseCursor.instance.setCursor(Cursors.leftRightCursor, this, -Cursors.leftRightCursor.width / 2, -Cursors.leftRightCursor.height / 2);							
							break;
						case DragType.Top:
						case DragType.Bottom:
							MouseCursor.instance.setCursor(Cursors.upDownCursor, this, -Cursors.upDownCursor.width / 2, -Cursors.upDownCursor.height / 2);							
							break;
						case DragType.RightTop:
						case DragType.LeftBottom:
							MouseCursor.instance.setCursor(Cursors.leftDownCursor, this, -Cursors.upDownCursor.width / 2, -Cursors.upDownCursor.height / 2);							
							break;
						case DragType.LeftTop:
						case DragType.RightBottom:
							MouseCursor.instance.setCursor(Cursors.rightDownCursor, this, -Cursors.rightDownCursor.width / 2, -Cursors.rightDownCursor.height / 2);
							break;
					}
				}
			}
		}

		private function lockDragType():void
		{	
			showDragType(this._dragType, true);
		}
		
		private function unlockDragType():void
		{
			MouseCursor.instance.unlockCursor(this);			
		}
		
		private function mouseDownHandler(event:MouseEvent):void
		{	
			this.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
			this.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
			this.removeEventListener(MouseEvent.ROLL_OUT, rollOutHandler);
			this.stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
			if (this._dragType != null)
			{
				this._lastMouseX = this.mouseX;
				this._lastMouseY = this.mouseY;				
			}
			switch (this._dragType)
			{
				case DragType.Move:
					this.startDrag();
					break;
				case DragType.LeftTop:
					this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveLeftTopHandler);
					break;
				case DragType.LeftBottom:
					this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveLeftBottomHandler);
					break;
				case DragType.RightTop:
					this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveRightTopHandler);
					break;
				case DragType.RightBottom:
					this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveRightBottomHandler);
					break;
				case DragType.Left:
					this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveLeftHandler);
					break;
				case DragType.Right:
					this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveRightHandler);
					break;
				case DragType.Bottom:
					this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveBottomHandler);
					break;
				case DragType.Top:
					this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveTopHandler);
					break;
			}			
			lockDragType();
		}
		
		private function mouseUpHandler(event:MouseEvent):void
		{
			this.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
			this.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
			this.addEventListener(MouseEvent.ROLL_OUT, rollOutHandler);
			
			this.stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
			switch (this._dragType)
			{
				case DragType.Move:
					this.stopDrag();
					break;
				case DragType.LeftTop:
					this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveLeftTopHandler);
					break;
				case DragType.LeftBottom:
					this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveLeftBottomHandler);
					break;
				case DragType.RightTop:
					this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveRightTopHandler);
					break;
				case DragType.RightBottom:
					this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveRightBottomHandler);
					break;
				case DragType.Left:
					this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveLeftHandler);
					break;
				case DragType.Right:
					this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveRightHandler);
					break;
				case DragType.Bottom:
					this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveBottomHandler);
					break;
				case DragType.Top:
					this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveTopHandler);
					break;
			}
			unlockDragType();
			if (this.hitTestPoint(event.stageX, event.stageY, true))
			{
				mouseMoveHandler(); //Refresh the cursor in case we got dragged over something valid.
			}
		}
		
		private function clampWidthDelta(dx:Number):Number
		{
			return this._desiredWidth + dx < (this._borderLeft + this._borderRight) ? this._desiredWidth - (this._borderLeft + this._borderRight) : dx;
		}
		
		private function clampHeightDelta(dy:Number):Number
		{
			return (this._desiredHeight + dy) < (this._borderTop + this._borderMoveTop + this._borderBottom)
				? this._desiredHeight - (this._borderTop + this._borderMoveTop + this._borderBottom) 
				: dy;			
		}
		
		private function mouseMoveLeftTopHandler(event:MouseEvent):void
		{
			var dx:Number = clampWidthDelta(this._lastMouseX - this.mouseX);
			this.width = this._desiredWidth + dx;
			this.x = int(this.x - dx);
			
			var dy:Number = clampHeightDelta(this._lastMouseY - this.mouseY);
			this.height = this._desiredHeight + dy;
			this.y = int(this.y - dy);
			
			this._lastMouseX = this.mouseX;
			this._lastMouseY = this.mouseY;
		}
		
		private function mouseMoveLeftBottomHandler(event:MouseEvent):void
		{
			var dx:Number = clampWidthDelta(this._lastMouseX - this.mouseX);
			this.width = this._desiredWidth + dx;
			this.x = int(this.x - dx);
			
			this.height = this._desiredHeight + clampHeightDelta(this.mouseY - this._lastMouseY);
			
			this._lastMouseX = this.mouseX;
			this._lastMouseY = this.mouseY;
		}
		
		private function mouseMoveRightTopHandler(event:MouseEvent):void
		{
			this.width = this._desiredWidth + clampWidthDelta(this.mouseX - this._lastMouseX);

			var dy:Number = clampHeightDelta(this._lastMouseY - this.mouseY);
			this.height = this._desiredHeight + dy;
			this.y = int(this.y - dy);
			
			this._lastMouseX = this.mouseX;
			this._lastMouseY = this.mouseY;
		}
		
		private function mouseMoveRightBottomHandler(event:MouseEvent):void
		{
			this.width = this._desiredWidth + clampWidthDelta(this.mouseX - this._lastMouseX);
			this.height = this._desiredHeight + clampHeightDelta(this.mouseY - this._lastMouseY);
			this._lastMouseX = this.mouseX;
			this._lastMouseY = this.mouseY;
		}
		
		private function mouseMoveLeftHandler(event:MouseEvent):void
		{
			var dx:Number = clampWidthDelta(this._lastMouseX - this.mouseX);
			this.width = this._desiredWidth + dx;			
			this.x = int(this.x - dx);
			this._lastMouseX = this.mouseX;
			this._lastMouseY = this.mouseY;
		}
		
		private function mouseMoveRightHandler(event:MouseEvent):void
		{	
			this.width = this._desiredWidth + clampWidthDelta(this.mouseX - this._lastMouseX);
			this._lastMouseX = this.mouseX;
			this._lastMouseY = this.mouseY;
		}
		
		private function mouseMoveTopHandler(event:MouseEvent):void
		{
			var dy:Number = clampHeightDelta(this._lastMouseY - this.mouseY);
			this.height = this._desiredHeight + dy;
			this.y = int(this.y - dy);
			this._lastMouseX = this.mouseX;
			this._lastMouseY = this.mouseY;
		}
		
		private function mouseMoveBottomHandler(event:MouseEvent):void
		{	
			this.height = this._desiredHeight + clampHeightDelta(this.mouseY - this._lastMouseY);
			this._lastMouseX = this.mouseX;
			this._lastMouseY = this.mouseY;
		}
	}
}

class DragType
{
	public static const Move:DragType = new DragType();
	public static const Top:DragType = new DragType();
	public static const Left:DragType = new DragType();
	public static const Right:DragType = new DragType();
	public static const LeftBottom:DragType = new DragType();
	public static const LeftTop:DragType = new DragType();
	public static const RightBottom:DragType = new DragType();
	public static const RightTop:DragType = new DragType();
	public static const Bottom:DragType = new DragType();
}